/*
 * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>
#include <stdbool.h>

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "freertos/idf_additions.h"

#include "sdkconfig.h"

#if CONFIG_I2S_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include "esp_log.h"

#include "hal/i2s_periph.h"
#include "soc/soc_caps.h"
#include "soc/soc_caps_full.h"
#include "hal/i2s_hal.h"
#include "hal/hal_utils.h"
#include "hal/dma_types.h"
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#endif

#if I2S_LL_GET(ADC_DAC_CAPABLE)
#include "hal/adc_ll.h"
#endif
#if SOC_I2S_SUPPORTS_APLL
#include "hal/clk_tree_ll.h"
#include "clk_ctrl_os.h"
#endif

#include "esp_private/i2s_platform.h"
#include "esp_private/esp_clk.h"
#if SOC_HAS(PAU)
#include "esp_private/sleep_retention.h"
#endif

#include "driver/gpio.h"
#include "esp_private/gpio.h"
#include "esp_private/i2s_sync.h"
#include "driver/i2s_common.h"
#include "i2s_private.h"

#if CONFIG_IDF_TARGET_ESP32
#include "esp_clock_output.h"
#endif

#include "clk_ctrl_os.h"
#include "esp_clk_tree.h"
#include "esp_intr_alloc.h"
#include "esp_check.h"
#include "esp_attr.h"
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#include "esp_cache.h"
#endif

#include "esp_rom_gpio.h"
#include "esp_memory_utils.h"

/* The actual max size of DMA buffer is 4095
 * Reserve several bytes for alignment, so that the position of the slot data in the buffer will be relatively fixed */
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#define I2S_DMA_BUFFER_MAX_SIZE     DMA_DESCRIPTOR_BUFFER_MAX_SIZE_64B_ALIGNED
#else
#define I2S_DMA_BUFFER_MAX_SIZE     DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED
#endif  // SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE

static const char *TAG = "i2s_common";

__attribute__((always_inline))
inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size)
{
    return heap_caps_aligned_calloc(4, num, size, I2S_DMA_ALLOC_CAPS);
}

/*---------------------------------------------------------------------------
                             I2S Static APIs
 ----------------------------------------------------------------------------
    Scope: This file only
 ----------------------------------------------------------------------------*/

#if I2S_USE_RETENTION_LINK
static esp_err_t s_i2s_create_sleep_retention_link_cb(void *arg)
{
    i2s_controller_t *i2s_obj = (i2s_controller_t *)arg;
    ESP_RETURN_ON_ERROR(sleep_retention_entries_create(i2s_reg_retention_info[i2s_obj->id].entry_array,
                                                       i2s_reg_retention_info[i2s_obj->id].array_size,
                                                       REGDMA_LINK_PRI_I2S, i2s_obj->slp_retention_mod),
                        TAG, "create retention link failed");
    return ESP_OK;
}

static void s_i2s_create_retention_module(i2s_controller_t *i2s_obj)
{
    sleep_retention_module_t module = i2s_obj->slp_retention_mod;

    _lock_acquire(&i2s_obj->mutex);
    if (i2s_obj->retention_link_created == false) {
        if (sleep_retention_module_allocate(module) != ESP_OK) {
            // even though the sleep retention module create failed, I2S driver should still work, so just warning here
            ESP_LOGW(TAG, "create retention module failed, power domain can't turn off");
        } else {
            i2s_obj->retention_link_created = true;
        }
    }
    _lock_release(&i2s_obj->mutex);
}
#endif // I2S_USE_RETENTION_LINK

static void i2s_tx_channel_start(i2s_chan_handle_t handle)
{
    i2s_hal_tx_reset(&(handle->controller->hal));
#if SOC_GDMA_SUPPORTED
    gdma_reset((handle->dma.dma_chan));
#else
    i2s_hal_tx_reset_dma(&(handle->controller->hal));
#endif
    i2s_hal_tx_reset_fifo(&(handle->controller->hal));
#if SOC_GDMA_SUPPORTED
    gdma_start((handle->dma.dma_chan), (uint32_t) handle->dma.desc[0]);
#else
    esp_intr_enable(handle->dma.dma_chan);
    i2s_hal_tx_enable_intr(&(handle->controller->hal));
    i2s_hal_tx_enable_dma(&(handle->controller->hal));
    i2s_hal_tx_start_link(&(handle->controller->hal), (uint32_t) handle->dma.desc[0]);
#endif
    if (!handle->is_etm_start) {
        i2s_hal_tx_start(&(handle->controller->hal));
    }
}

static void i2s_rx_channel_start(i2s_chan_handle_t handle)
{
    i2s_hal_rx_reset(&(handle->controller->hal));
#if SOC_GDMA_SUPPORTED
    gdma_reset(handle->dma.dma_chan);
#else
    i2s_hal_rx_reset_dma(&(handle->controller->hal));
#endif
    i2s_hal_rx_reset_fifo(&(handle->controller->hal));
#if SOC_GDMA_SUPPORTED
    gdma_start(handle->dma.dma_chan, (uint32_t) handle->dma.desc[0]);
#else
    esp_intr_enable(handle->dma.dma_chan);
    i2s_hal_rx_enable_intr(&(handle->controller->hal));
    i2s_hal_rx_enable_dma(&(handle->controller->hal));
    i2s_hal_rx_start_link(&(handle->controller->hal), (uint32_t) handle->dma.desc[0]);
#endif
    if (!handle->is_etm_start) {
        i2s_hal_rx_start(&(handle->controller->hal));
    }
}

static void i2s_tx_channel_stop(i2s_chan_handle_t handle)
{
    if (!handle->is_etm_stop) {
        i2s_hal_tx_stop(&(handle->controller->hal));
    }
#if SOC_GDMA_SUPPORTED
    gdma_stop(handle->dma.dma_chan);
#else
    i2s_hal_tx_stop_link(&(handle->controller->hal));
    i2s_hal_tx_disable_intr(&(handle->controller->hal));
    i2s_hal_tx_disable_dma(&(handle->controller->hal));
    esp_intr_disable(handle->dma.dma_chan);
#endif
}

static void i2s_rx_channel_stop(i2s_chan_handle_t handle)
{
    if (!handle->is_etm_stop) {
        i2s_hal_rx_stop(&(handle->controller->hal));
    }
#if SOC_GDMA_SUPPORTED
    gdma_stop(handle->dma.dma_chan);
#else
    i2s_hal_rx_stop_link(&(handle->controller->hal));
    i2s_hal_rx_disable_intr(&(handle->controller->hal));
    i2s_hal_rx_disable_dma(&(handle->controller->hal));
    esp_intr_disable(handle->dma.dma_chan);
#endif
}

static esp_err_t i2s_destroy_controller_obj(i2s_controller_t **i2s_obj)
{
    I2S_NULL_POINTER_CHECK(TAG, i2s_obj);
    I2S_NULL_POINTER_CHECK(TAG, *i2s_obj);
    ESP_RETURN_ON_FALSE(!(*i2s_obj)->rx_chan && !(*i2s_obj)->tx_chan,
                        ESP_ERR_INVALID_STATE, TAG,
                        "there still have channels under this i2s controller");
    int id = (*i2s_obj)->id;
#if CONFIG_IDF_TARGET_ESP32
    if ((*i2s_obj)->mclk_out_hdl) {
        esp_clock_output_stop((*i2s_obj)->mclk_out_hdl);
    }
#endif
#if SOC_I2S_HW_VERSION_1
    i2s_ll_enable_dma((*i2s_obj)->hal.dev, false);
#endif
#if I2S_USE_RETENTION_LINK
    if ((*i2s_obj)->slp_retention_mod) {
        if ((*i2s_obj)->retention_link_created) {
            sleep_retention_module_free((*i2s_obj)->slp_retention_mod);
        }
        sleep_retention_module_deinit((*i2s_obj)->slp_retention_mod);
    }
#endif  // I2S_USE_RETENTION_LINK
    free(*i2s_obj);
    *i2s_obj = NULL;
    return i2s_platform_release_occupation(I2S_CTLR_HP, id);
}

/**
 * @brief Acquire i2s controller object
 *
 * @param id        i2s port id
 * @param search_reverse   reverse the sequence of port acquirement
 *                  set false to acquire from I2S_NUM_0 first
 *                  set true to acquire from I2S_LL_GET(INST_NUM) - 1 first
 * @return
 *      - pointer of acquired i2s controller object
 */
static i2s_controller_t *i2s_acquire_controller_obj(int id)
{
    if (id < 0 || id >= I2S_LL_GET(INST_NUM)) {
        return NULL;
    }
    /* pre-alloc controller object */
    i2s_controller_t *pre_alloc = (i2s_controller_t *)heap_caps_calloc(1, sizeof(i2s_controller_t), I2S_MEM_ALLOC_CAPS);
    if (pre_alloc == NULL) {
        return NULL;
    }
    pre_alloc->id = id;
    i2s_hal_init(&pre_alloc->hal, id);
    pre_alloc->full_duplex = false;
    pre_alloc->tx_chan = NULL;
    pre_alloc->rx_chan = NULL;
    pre_alloc->mclk = I2S_GPIO_UNUSED;

    i2s_controller_t *i2s_obj = NULL;
    /* Try to occupy this i2s controller */
    if (i2s_platform_acquire_occupation(I2S_CTLR_HP, id, "i2s_driver") == ESP_OK) {
        portENTER_CRITICAL(&g_i2s.spinlock);
        i2s_obj = pre_alloc;
        g_i2s.controller[id] = i2s_obj;
        portEXIT_CRITICAL(&g_i2s.spinlock);
#if I2S_LL_GET(ADC_DAC_CAPABLE)
        if (id == I2S_NUM_0) {
            adc_ll_digi_set_data_source(0);
        }
#endif

#if I2S_USE_RETENTION_LINK
        sleep_retention_module_t module = i2s_reg_retention_info[id].retention_module;
        sleep_retention_module_init_param_t init_param = {
            .cbs = {
                .create = {
                    .handle = s_i2s_create_sleep_retention_link_cb,
                    .arg = i2s_obj,
                },
            },
            .depends = RETENTION_MODULE_BITMAP_INIT(CLOCK_SYSTEM)
        };
        if (sleep_retention_module_init(module, &init_param) == ESP_OK) {
            i2s_obj->slp_retention_mod = module;
        } else {
            // even the sleep retention module init failed, I2S driver should still work, so just warning here
            ESP_LOGW(TAG, "init sleep retention failed for I2S%d, power domain may be turned off during sleep", id);
        }
#endif  // I2S_USE_RETENTION_LINK
    } else {
        free(pre_alloc);
        portENTER_CRITICAL(&g_i2s.spinlock);
        if (g_i2s.controller[id]) {
            i2s_obj = g_i2s.controller[id];
        }
        portEXIT_CRITICAL(&g_i2s.spinlock);
        if (i2s_obj == NULL) {
            ESP_LOGE(TAG, "i2s%d might be occupied by other component", id);
        }
    }

    return i2s_obj;
}

static inline bool i2s_take_available_channel(i2s_controller_t *i2s_obj, uint8_t chan_search_mask)
{
    bool is_available = false;

    portENTER_CRITICAL(&g_i2s.spinlock);
    if (!(chan_search_mask & i2s_obj->chan_occupancy)) {
        i2s_obj->chan_occupancy |= chan_search_mask;
        is_available = true;
    }
    portEXIT_CRITICAL(&g_i2s.spinlock);
    return is_available;
}

static esp_err_t i2s_register_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir, uint32_t desc_num)
{
    I2S_NULL_POINTER_CHECK(TAG, i2s_obj);

    esp_err_t ret = ESP_OK;

    i2s_chan_handle_t new_chan = (i2s_chan_handle_t)heap_caps_calloc(1, sizeof(struct i2s_channel_obj_t), I2S_MEM_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(new_chan, ESP_ERR_NO_MEM, TAG, "No memory for new channel");
    new_chan->mode = I2S_COMM_MODE_NONE;
    new_chan->role = I2S_ROLE_MASTER; // Set default role to master
    new_chan->dir = dir;
    new_chan->state = I2S_CHAN_STATE_REGISTER;
#if SOC_I2S_SUPPORTS_APLL
    new_chan->apll_en = false;
#endif
    new_chan->mode_info = NULL;
    new_chan->controller = i2s_obj;
#if CONFIG_PM_ENABLE
    new_chan->pm_lock = NULL; // Init in i2s_set_clock according to clock source
#endif
    new_chan->msg_queue = xQueueCreateWithCaps(desc_num - 1, sizeof(uint8_t *), I2S_MEM_ALLOC_CAPS);
    ESP_GOTO_ON_FALSE(new_chan->msg_queue, ESP_ERR_NO_MEM, err, TAG, "No memory for message queue");
    new_chan->mutex = xSemaphoreCreateMutexWithCaps(I2S_MEM_ALLOC_CAPS);
    ESP_GOTO_ON_FALSE(new_chan->mutex, ESP_ERR_NO_MEM, err, TAG, "No memory for mutex semaphore");
    new_chan->binary = xSemaphoreCreateBinaryWithCaps(I2S_MEM_ALLOC_CAPS);
    ESP_GOTO_ON_FALSE(new_chan->binary, ESP_ERR_NO_MEM, err, TAG, "No memory for binary semaphore");

    new_chan->callbacks.on_recv = NULL;
    new_chan->callbacks.on_recv_q_ovf = NULL;
    new_chan->callbacks.on_sent = NULL;
    new_chan->callbacks.on_send_q_ovf = NULL;
    new_chan->dma.rw_pos = 0;
    new_chan->dma.curr_ptr = NULL;
    new_chan->start = NULL;
    new_chan->stop = NULL;
    new_chan->reserve_gpio_mask = 0;

    if (dir == I2S_DIR_TX) {
        if (i2s_obj->tx_chan) {
            i2s_del_channel(i2s_obj->tx_chan);
        }
        i2s_obj->tx_chan = new_chan;

    } else {
        if (i2s_obj->rx_chan) {
            i2s_del_channel(i2s_obj->rx_chan);
        }
        i2s_obj->rx_chan = new_chan;
    }
    return ret;
err:
    if (new_chan->msg_queue) {
        vQueueDeleteWithCaps(new_chan->msg_queue);
    }
    if (new_chan->mutex) {
        vSemaphoreDeleteWithCaps(new_chan->mutex);
    }
    if (new_chan->binary) {
        vSemaphoreDeleteWithCaps(new_chan->binary);
    }
    free(new_chan);

    return ret;
}

#if SOC_I2S_HW_VERSION_1
esp_err_t i2s_channel_change_port(i2s_chan_handle_t handle, int id)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    ESP_RETURN_ON_FALSE(id >= 0 && id < I2S_LL_GET(INST_NUM), ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id");
    if (id == handle->controller->id) {
        return ESP_OK;
    }
    i2s_controller_t *i2s_obj = i2s_acquire_controller_obj(id);
    if (!i2s_obj || !i2s_take_available_channel(i2s_obj, handle->dir)) {
        return ESP_ERR_NOT_FOUND;
    }
    i2s_controller_t *old_i2s_obj = handle->controller;
    portENTER_CRITICAL(&g_i2s.spinlock);
    if (handle->dir == I2S_DIR_TX) {
        i2s_obj->tx_chan = handle;
        i2s_obj->chan_occupancy |= I2S_DIR_TX;
        old_i2s_obj->tx_chan = NULL;
        old_i2s_obj->full_duplex = false;
        old_i2s_obj->chan_occupancy &= ~I2S_DIR_TX;
    } else {
        i2s_obj->rx_chan = handle;
        i2s_obj->chan_occupancy |= I2S_DIR_RX;
        old_i2s_obj->rx_chan = NULL;
        old_i2s_obj->full_duplex = false;
        old_i2s_obj->chan_occupancy &= ~I2S_DIR_RX;
    }
    handle->controller = i2s_obj;
    portEXIT_CRITICAL(&g_i2s.spinlock);

    return ESP_OK;
}
#endif

#ifndef __cplusplus
/* To make sure the i2s_event_callbacks_t is same size as i2s_event_callbacks_internal_t */
_Static_assert(sizeof(i2s_event_callbacks_t) == sizeof(i2s_event_callbacks_internal_t), "Invalid size of i2s_event_callbacks_t structure");
#endif

esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2s_event_callbacks_t *callbacks, void *user_data)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    I2S_NULL_POINTER_CHECK(TAG, callbacks);
    esp_err_t ret = ESP_OK;
#if CONFIG_I2S_ISR_IRAM_SAFE
    if (callbacks->on_recv) {
        ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_recv), ESP_ERR_INVALID_ARG, TAG, "on_recv callback not in IRAM");
    }
    if (callbacks->on_recv_q_ovf) {
        ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_recv_q_ovf), ESP_ERR_INVALID_ARG, TAG, "on_recv_q_ovf callback not in IRAM");
    }
    if (callbacks->on_sent) {
        ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_sent), ESP_ERR_INVALID_ARG, TAG, "on_sent callback not in IRAM");
    }
    if (callbacks->on_send_q_ovf) {
        ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_send_q_ovf), ESP_ERR_INVALID_ARG, TAG, "on_send_q_ovf callback not in IRAM");
    }
    if (user_data) {
        ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
    }
#endif

    xSemaphoreTake(handle->mutex, portMAX_DELAY);
    ESP_GOTO_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, "invalid state, I2S has enabled");
    memcpy(&(handle->callbacks), callbacks, sizeof(i2s_event_callbacks_t));
    handle->user_data = user_data;
err:
    xSemaphoreGive(handle->mutex);
    return ret;
}

uint32_t i2s_get_buf_size(i2s_chan_handle_t handle, uint32_t data_bit_width, uint32_t dma_frame_num)
{
    uint32_t active_chan = handle->active_slot;
#if CONFIG_IDF_TARGET_ESP32
    uint32_t bytes_per_sample = ((data_bit_width + 15) / 16) * 2;
#else
    uint32_t bytes_per_sample = (data_bit_width + 7) / 8;
#endif  // CONFIG_IDF_TARGET_ESP32
    uint32_t bytes_per_frame = bytes_per_sample * active_chan;
    if (bytes_per_frame == 0) {
        return 0;
    }
    uint32_t bufsize = dma_frame_num * bytes_per_frame;
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
    /* bufsize need to align with cache line size */
    uint32_t alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
    uint32_t aligned_frame_num = dma_frame_num;
    /* To make the buffer aligned with the cache line size, search for the ceil aligned size first,
       If the buffer size exceed the max DMA buffer size, toggle the sign to search for the floor aligned size */
    for (int sign = 1; bufsize % alignment != 0; aligned_frame_num += sign) {
        bufsize = aligned_frame_num * bytes_per_frame;
        /* If the buffer size exceed the max dma size */
        if (bufsize > I2S_DMA_BUFFER_MAX_SIZE && sign == 1) {
            sign = -1; // toggle the search sign
            aligned_frame_num = dma_frame_num;              // Reset the frame num
            bufsize = aligned_frame_num * bytes_per_frame;  // Reset the bufsize
        }
    }
    if (bufsize / bytes_per_frame != dma_frame_num) {
        ESP_LOGW(TAG, "dma frame num is adjusted to %"PRIu32" to align the dma buffer with %"PRIu32
                 ", bufsize = %"PRIu32, bufsize / bytes_per_frame, alignment, bufsize);
    }
#endif
    /* Limit DMA buffer size if it is out of range */
    if (bufsize > I2S_DMA_BUFFER_MAX_SIZE) {
        uint32_t frame_num = I2S_DMA_BUFFER_MAX_SIZE / bytes_per_frame;
        bufsize = frame_num * bytes_per_frame;
        ESP_LOGW(TAG, "dma frame num is out of dma buffer size, limited to %"PRIu32, frame_num);
    }
    return bufsize;
}

esp_err_t i2s_free_dma_desc(i2s_chan_handle_t handle)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    handle->dma.buf_size = 0;

    if (handle->dma.desc) {
        for (int i = 0; i < handle->dma.desc_num; i++) {
            if (handle->dma.desc[i]) {
                free(handle->dma.desc[i]);
                handle->dma.desc[i] = NULL;
            }
        }
        free(handle->dma.desc);
        handle->dma.desc = NULL;
    }

    if (handle->dma.bufs) {
        for (int i = 0; i < handle->dma.desc_num; i++) {
            if (handle->dma.bufs[i]) {
                free(handle->dma.bufs[i]);
                handle->dma.bufs[i] = NULL;
            }
        }
        free(handle->dma.bufs);
        handle->dma.bufs = NULL;
    }

    return ESP_OK;
}

esp_err_t i2s_alloc_dma_desc(i2s_chan_handle_t handle, uint32_t bufsize)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    esp_err_t ret = ESP_OK;
    ESP_RETURN_ON_FALSE(bufsize <= I2S_DMA_BUFFER_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "dma buffer can't be bigger than %d", I2S_DMA_BUFFER_MAX_SIZE);
    uint32_t num = handle->dma.desc_num;
    handle->dma.buf_size = bufsize;

    /* Descriptors must be in the internal RAM */
    handle->dma.desc = (lldesc_t **)heap_caps_calloc(num, sizeof(lldesc_t *), I2S_MEM_ALLOC_CAPS);
    ESP_GOTO_ON_FALSE(handle->dma.desc, ESP_ERR_NO_MEM, err, TAG, "create I2S DMA descriptor array failed");
    handle->dma.bufs = (uint8_t **)heap_caps_calloc(num, sizeof(uint8_t *), I2S_MEM_ALLOC_CAPS);
    ESP_GOTO_ON_FALSE(handle->dma.bufs, ESP_ERR_NO_MEM, err, TAG, "create I2S DMA buffer array failed");
    for (int i = 0; i < num; i++) {
        /* Allocate DMA descriptor */
        handle->dma.desc[i] = (lldesc_t *) i2s_dma_calloc(handle, 1, sizeof(lldesc_t));
        ESP_GOTO_ON_FALSE(handle->dma.desc[i], ESP_ERR_NO_MEM, err, TAG,  "allocate DMA description failed");
        handle->dma.desc[i]->owner = 1;
        handle->dma.desc[i]->eof = 1;
        handle->dma.desc[i]->sosf = 0;
        handle->dma.desc[i]->length = bufsize;
        handle->dma.desc[i]->size = bufsize;
        handle->dma.desc[i]->offset = 0;
        handle->dma.bufs[i] = (uint8_t *) i2s_dma_calloc(handle, 1, bufsize * sizeof(uint8_t));
        ESP_GOTO_ON_FALSE(handle->dma.bufs[i], ESP_ERR_NO_MEM, err, TAG,  "allocate DMA buffer failed");
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
        esp_cache_msync(handle->dma.bufs[i], bufsize * sizeof(uint8_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M);
#endif
        handle->dma.desc[i]->buf = handle->dma.bufs[i];
        ESP_LOGV(TAG, "desc addr: %8p\tbuffer addr:%8p", handle->dma.desc[i], handle->dma.bufs[i]);
    }
    /* Connect DMA descriptor as a circle */
    for (int i = 0; i < num; i++) {
        /* Link to the next descriptor */
        STAILQ_NEXT(handle->dma.desc[i], qe) = (i < (num - 1)) ? (handle->dma.desc[i + 1]) : handle->dma.desc[0];
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
        esp_cache_msync(handle->dma.desc[i], sizeof(lldesc_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
#endif
    }
    if (handle->dir == I2S_DIR_RX) {
        i2s_ll_rx_set_eof_num(handle->controller->hal.dev, bufsize);
    }
    ESP_LOGD(TAG, "DMA malloc info: dma_desc_num = %"PRIu32", dma_desc_buf_size = dma_frame_num * slot_num * data_bit_width = %"PRIu32, num, bufsize);
    return ESP_OK;
err:
    i2s_free_dma_desc(handle);
    return ret;
}

#if SOC_I2S_SUPPORTS_APLL
static uint32_t i2s_set_get_apll_freq(uint32_t mclk_freq_hz)
{
    /* Calculate the expected APLL  */
    int mclk_div = (int)((CLK_LL_APLL_MIN_HZ / mclk_freq_hz) + 1);
    /* apll_freq = mclk * div
        * when div = 1, hardware will still divide 2
        * when div = 0, the final mclk will be unpredictable
        * So the div here should be at least 2 */
    mclk_div = mclk_div < 2 ? 2 : mclk_div;
    uint32_t expt_freq = mclk_freq_hz * mclk_div;
    if (expt_freq > CLK_LL_APLL_MAX_HZ) {
        ESP_LOGE(TAG, "The required APLL frequency exceed its maximum value");
        return 0;
    }
    uint32_t real_freq = 0;
    esp_err_t ret = periph_rtc_apll_freq_set(expt_freq, &real_freq);
    if (ret == ESP_ERR_INVALID_ARG) {
        ESP_LOGE(TAG, "set APLL freq failed due to invalid argument");
        return 0;
    }
    if (ret == ESP_ERR_INVALID_STATE) {
        ESP_LOGW(TAG, "APLL is occupied already, it is working at %"PRIu32" Hz while the expected frequency is %"PRIu32" Hz", real_freq, expt_freq);
        ESP_LOGW(TAG, "Trying to work at %"PRIu32" Hz...", real_freq);
    }
    ESP_LOGD(TAG, "APLL expected frequency is %"PRIu32" Hz, real frequency is %"PRIu32" Hz", expt_freq, real_freq);
    return real_freq;
}
#endif

uint32_t i2s_get_source_clk_freq(i2s_clock_src_t clk_src, uint32_t mclk_freq_hz)
{
    uint32_t clk_freq = 0;
#if SOC_I2S_SUPPORTS_APLL
    if (clk_src == I2S_CLK_SRC_APLL) {
        return i2s_set_get_apll_freq(mclk_freq_hz);
    }
#endif
#ifdef I2S_LL_DEFAULT_CLK_SRC
    if (clk_src == I2S_CLK_SRC_DEFAULT) {
        clk_src = I2S_LL_DEFAULT_CLK_SRC;
    }
#endif
    esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &clk_freq);
    return clk_freq;
}

#if SOC_GDMA_SUPPORTED
static bool i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
    i2s_chan_handle_t handle = (i2s_chan_handle_t)user_data;
    BaseType_t need_yield1 = 0;
    BaseType_t need_yield2 = 0;
    BaseType_t user_need_yield = 0;
    lldesc_t *finish_desc;
    uint32_t dummy;

    finish_desc = (lldesc_t *)event_data->rx_eof_desc_addr;
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
    esp_cache_msync((void *)finish_desc->buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE);
#endif
    i2s_event_data_t evt = {
        .dma_buf = (void *)finish_desc->buf,
        .size = handle->dma.buf_size,
    };
    if (handle->callbacks.on_recv) {
        user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data);
    }
    if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
        xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
        if (handle->callbacks.on_recv_q_ovf) {
            user_need_yield |= handle->callbacks.on_recv_q_ovf(handle, &evt, handle->user_data);
        }
    }
    xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2);

    return need_yield1 | need_yield2 | user_need_yield;
}

static bool i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
    i2s_chan_handle_t handle = (i2s_chan_handle_t)user_data;
    BaseType_t need_yield1 = 0;
    BaseType_t need_yield2 = 0;
    BaseType_t user_need_yield = 0;
    lldesc_t *finish_desc;
    uint32_t dummy;

    finish_desc = (lldesc_t *)event_data->tx_eof_desc_addr;
    void *curr_buf = (void *)finish_desc->buf;
    i2s_event_data_t evt = {
        .dma_buf = curr_buf,
        .size = handle->dma.buf_size,
    };
    if (handle->dma.auto_clear_before_cb) {
        memset(curr_buf, 0, handle->dma.buf_size);
    }
    if (handle->callbacks.on_sent) {
        user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data);
    }
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
    /* Sync buffer after the callback in case users update the buffer in the callback */
    if (handle->dma.auto_clear_before_cb || handle->callbacks.on_sent) {
        esp_cache_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
    }
#endif
    if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
        xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
        if (handle->callbacks.on_send_q_ovf) {
            evt.dma_buf = NULL;
            user_need_yield |= handle->callbacks.on_send_q_ovf(handle, &evt, handle->user_data);
        }
    }
    if (handle->dma.auto_clear_after_cb) {
        memset(curr_buf, 0, handle->dma.buf_size);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
        esp_cache_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
#endif
    }
    xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2);

    return need_yield1 | need_yield2 | user_need_yield;
}

#else

static void i2s_dma_rx_callback(void *arg)
{
    BaseType_t need_yield1 = 0;
    BaseType_t need_yield2 = 0;
    BaseType_t user_need_yield = 0;
    lldesc_t *finish_desc = NULL;
    i2s_event_data_t evt;
    i2s_chan_handle_t handle = (i2s_chan_handle_t)arg;
    uint32_t dummy;

    uint32_t status = i2s_hal_get_intr_status(&(handle->controller->hal));
    i2s_hal_clear_intr_status(&(handle->controller->hal), status);
    if (!status) {
        return;
    }

    if (handle && (status & I2S_LL_EVENT_RX_EOF)) {
        i2s_hal_get_in_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc);
        evt.dma_buf = (void *)finish_desc->buf;
        evt.size = handle->dma.buf_size;
        if (handle->callbacks.on_recv) {
            user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data);
        }
        if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
            xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
            if (handle->callbacks.on_recv_q_ovf) {
                evt.dma_buf = NULL;
                user_need_yield |= handle->callbacks.on_recv_q_ovf(handle, &evt, handle->user_data);
            }
        }
        xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2);
    }

    if (need_yield1 || need_yield2 || user_need_yield) {
        portYIELD_FROM_ISR();
    }
}

static void i2s_dma_tx_callback(void *arg)
{
    BaseType_t need_yield1 = 0;
    BaseType_t need_yield2 = 0;
    BaseType_t user_need_yield = 0;
    lldesc_t *finish_desc = NULL;
    i2s_event_data_t evt;
    i2s_chan_handle_t handle = (i2s_chan_handle_t)arg;
    uint32_t dummy;

    uint32_t status = i2s_hal_get_intr_status(&(handle->controller->hal));
    i2s_hal_clear_intr_status(&(handle->controller->hal), status);
    if (!status) {
        return;
    }

    if (handle && (status & I2S_LL_EVENT_TX_EOF)) {
        i2s_hal_get_out_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc);
        void *curr_buf = (void *)finish_desc->buf;
        evt.dma_buf = curr_buf;
        evt.size = handle->dma.buf_size;
        // Auto clear the dma buffer before data sent
        if (handle->dma.auto_clear_before_cb) {
            memset(curr_buf, 0, handle->dma.buf_size);
        }
        if (handle->callbacks.on_sent) {
            user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data);
        }
        if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
            xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
            if (handle->callbacks.on_send_q_ovf) {
                user_need_yield |= handle->callbacks.on_send_q_ovf(handle, &evt, handle->user_data);
            }
        }
        // Auto clear the dma buffer after data sent
        if (handle->dma.auto_clear_after_cb) {
            memset(curr_buf, 0, handle->dma.buf_size);
        }
        xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2);
    }

    if (need_yield1 || need_yield2 || user_need_yield) {
        portYIELD_FROM_ISR();
    }
}
#endif

#if SOC_GDMA_SUPPORTED
/**
 * @brief   I2S DMA interrupt initialization (implemented by I2S dedicated DMA)
 * @note    I2S will use GDMA if chip supports, and the interrupt is triggered by GDMA.
 *
 * @param   handle      I2S channel handle
 * @param   intr_flag   Interrupt allocation flag
 * @return
 *      - ESP_OK                    I2S DMA interrupt initialize success
 *      - ESP_ERR_NOT_FOUND         GDMA channel not found
 *      - ESP_ERR_INVALID_ARG       Invalid arguments
 *      - ESP_ERR_INVALID_STATE     GDMA state error
 */
esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag)
{
    esp_err_t ret = ESP_OK;
    int port_id = handle->controller->id;
    ESP_RETURN_ON_FALSE((port_id >= 0) && (port_id < I2S_LL_GET(INST_NUM)), ESP_ERR_INVALID_ARG, TAG, "invalid handle");
    /* Set GDMA trigger module */
    gdma_trigger_t trig = {.periph = GDMA_TRIG_PERIPH_I2S};

    switch (port_id) {
#if I2S_LL_GET(INST_NUM) > 2
    case I2S_NUM_2:
        trig.instance_id = SOC_GDMA_TRIG_PERIPH_I2S2;
        break;
#endif
#if I2S_LL_GET(INST_NUM) > 1
    case I2S_NUM_1:
        trig.instance_id = SOC_GDMA_TRIG_PERIPH_I2S1;
        break;
#endif
    case I2S_NUM_0:
        trig.instance_id = SOC_GDMA_TRIG_PERIPH_I2S0;
        break;
    default:
        ESP_LOGE(TAG, "Unsupported I2S port number");
        return ESP_ERR_NOT_SUPPORTED;
    }

    /* Set GDMA config */
    gdma_channel_alloc_config_t dma_cfg = {
#if CONFIG_I2S_ISR_IRAM_SAFE
        .flags.isr_cache_safe = true,
#endif
    };
    if (handle->dir == I2S_DIR_TX) {
        dma_cfg.direction = GDMA_CHANNEL_DIRECTION_TX;
        /* Register a new GDMA tx channel */
        ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, &handle->dma.dma_chan), TAG, "Register tx dma channel error");
        ESP_GOTO_ON_ERROR(gdma_connect(handle->dma.dma_chan, trig), err1, TAG, "Connect tx dma channel error");
        gdma_tx_event_callbacks_t cb = {.on_trans_eof = i2s_dma_tx_callback};
        /* Set callback function for GDMA, the interrupt is triggered by GDMA, then the GDMA ISR will call the  callback function */
        ESP_GOTO_ON_ERROR(gdma_register_tx_event_callbacks(handle->dma.dma_chan, &cb, handle), err2, TAG, "Register tx callback failed");
    } else {
        dma_cfg.direction = GDMA_CHANNEL_DIRECTION_RX;
        /* Register a new GDMA rx channel */
        ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, &handle->dma.dma_chan), TAG, "Register rx dma channel error");
        ESP_GOTO_ON_ERROR(gdma_connect(handle->dma.dma_chan, trig), err1, TAG, "Connect rx dma channel error");
        gdma_rx_event_callbacks_t cb = {.on_recv_eof = i2s_dma_rx_callback};
        /* Set callback function for GDMA, the interrupt is triggered by GDMA, then the GDMA ISR will call the  callback function */
        ESP_GOTO_ON_ERROR(gdma_register_rx_event_callbacks(handle->dma.dma_chan, &cb, handle), err2, TAG, "Register rx callback failed");
    }
    return ret;
err2:
    gdma_disconnect(handle->dma.dma_chan);
err1:
    gdma_del_channel(handle->dma.dma_chan);
    handle->dma.dma_chan = NULL;
    return ret;
}
#else
/**
 * @brief   I2S DMA interrupt initialization (implemented by I2S dedicated DMA)
 * @note    I2S will use GDMA if chip supports, and the interrupt is triggered by GDMA.
 *
 * @param   handle      I2S channel handle
 * @param   intr_flag   Interrupt allocation flag
 * @return
 *      - ESP_OK                    I2S DMA interrupt initialize success
 *      - ESP_ERR_NOT_FOUND         GDMA channel not found
 *      - ESP_ERR_INVALID_ARG       Invalid arguments
 *      - ESP_ERR_INVALID_STATE     GDMA state error
 */
esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag)
{
    esp_err_t ret = ESP_OK;
    int port_id = handle->controller->id;
    ESP_RETURN_ON_FALSE((port_id >= 0) && (port_id < I2S_LL_GET(INST_NUM)), ESP_ERR_INVALID_ARG, TAG, "invalid handle");
    intr_flag |= handle->intr_prio_flags;
    /* Initialize I2S module interrupt */
    if (handle->dir == I2S_DIR_TX) {
        ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(i2s_periph_signal[port_id].irq, intr_flag,
                                                      (uint32_t)i2s_ll_get_interrupt_status_reg(handle->controller->hal.dev), I2S_LL_TX_EVENT_MASK,
                                                      i2s_dma_tx_callback, handle, &handle->dma.dma_chan), TAG, "Allocate tx dma channel failed");
    } else {
        ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(i2s_periph_signal[port_id].irq, intr_flag,
                                                      (uint32_t)i2s_ll_get_interrupt_status_reg(handle->controller->hal.dev), I2S_LL_RX_EVENT_MASK,
                                                      i2s_dma_rx_callback, handle, &handle->dma.dma_chan), TAG, "Allocate rx dma channel failed");
    }
    /* Start DMA */
    i2s_ll_enable_dma(handle->controller->hal.dev, true);
    return ret;
}
#endif  // SOC_GDMA_SUPPORTED

static uint64_t s_i2s_get_pair_chan_gpio_mask(i2s_chan_handle_t handle)
{
    if (handle->dir == I2S_DIR_TX) {
        return handle->controller->rx_chan ? handle->controller->rx_chan->reserve_gpio_mask : 0;
    }
    return handle->controller->tx_chan ? handle->controller->tx_chan->reserve_gpio_mask : 0;
}

void i2s_output_gpio_reserve(i2s_chan_handle_t handle, int gpio_num)
{
    bool used_by_pair_chan = false;
    /* If the gpio is used by the pair channel do not show warning for this case */
    if (handle->controller->full_duplex) {
        used_by_pair_chan = !!(s_i2s_get_pair_chan_gpio_mask(handle) & BIT64(gpio_num));
    }
    /* reserve the GPIO output path, because we don't expect another peripheral to signal to the same GPIO */
    if (!used_by_pair_chan && (esp_gpio_reserve(BIT64(gpio_num)) & BIT64(gpio_num))) {
        ESP_LOGW(TAG, "GPIO %d is not usable, maybe conflict with others", gpio_num);
    }
    handle->reserve_gpio_mask |= BIT64(gpio_num);
}

void i2s_output_gpio_revoke(i2s_chan_handle_t handle, uint64_t gpio_mask)
{
    uint64_t revoke_mask = gpio_mask;
    /* If the gpio is used by the pair channel do not show warning for this case */
    if (handle->controller->full_duplex) {
        uint64_t pair_chan_gpio_mask = s_i2s_get_pair_chan_gpio_mask(handle);
        /* Only revoke the gpio which is not used by the pair channel */
        revoke_mask = (pair_chan_gpio_mask ^ gpio_mask) & gpio_mask;
    }
    esp_gpio_revoke(revoke_mask);
    handle->reserve_gpio_mask &= ~gpio_mask;
}

void i2s_gpio_check_and_set(i2s_chan_handle_t handle, int gpio, uint32_t signal_idx, bool is_input, bool is_invert)
{
    /* Ignore the pin if pin = I2S_GPIO_UNUSED */
    if (gpio != (int)I2S_GPIO_UNUSED) {
        gpio_func_sel(gpio, PIN_FUNC_GPIO);
        if (is_input) {
            /* Enable the input, for some GPIOs, the input function are not enabled as default */
            gpio_input_enable(gpio);
            esp_rom_gpio_connect_in_signal(gpio, signal_idx, is_invert);
        } else {
            i2s_output_gpio_reserve(handle, gpio);
            /* output will be enabled in esp_rom_gpio_connect_out_signal */
            esp_rom_gpio_connect_out_signal(gpio, signal_idx, is_invert, 0);
        }
    }
}

void i2s_gpio_loopback_set(i2s_chan_handle_t handle, int gpio, uint32_t out_sig_idx, uint32_t in_sig_idx)
{
    if (gpio != (int)I2S_GPIO_UNUSED) {
        i2s_output_gpio_reserve(handle,  gpio);
        gpio_func_sel(gpio, PIN_FUNC_GPIO);
        gpio_input_enable(gpio);
        esp_rom_gpio_connect_out_signal(gpio, out_sig_idx, 0, 0);
        esp_rom_gpio_connect_in_signal(gpio, in_sig_idx, 0);
    }
}

esp_err_t i2s_check_set_mclk(i2s_chan_handle_t handle, int id, int gpio_num, i2s_clock_src_t clk_src, bool is_invert)
{
    if (gpio_num == (int)I2S_GPIO_UNUSED) {
        return ESP_OK;
    }
#if CONFIG_IDF_TARGET_ESP32
    bool is_i2s0 = id == I2S_NUM_0;
    bool is_apll = clk_src == I2S_CLK_SRC_APLL;
    if (g_i2s.controller[id]->mclk_out_hdl == NULL) {
        i2s_output_gpio_reserve(handle, gpio_num);
        soc_clkout_sig_id_t clkout_sig = is_apll ? CLKOUT_SIG_APLL : (is_i2s0 ? CLKOUT_SIG_I2S0 : CLKOUT_SIG_I2S1);
        ESP_RETURN_ON_ERROR(esp_clock_output_start(clkout_sig, gpio_num, &(g_i2s.controller[id]->mclk_out_hdl)), TAG, "mclk configure failed");
    }
#else
    ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "mck_io_num invalid");
#if SOC_I2S_HW_VERSION_2
    if (clk_src == I2S_CLK_SRC_EXTERNAL) {
        i2s_gpio_check_and_set(handle, gpio_num, i2s_periph_signal[id].mck_in_sig, true, is_invert);
    } else
#endif  // SOC_I2S_HW_VERSION_2
    {
        i2s_gpio_check_and_set(handle, gpio_num, i2s_periph_signal[id].mck_out_sig, false, is_invert);
    }
#endif  // CONFIG_IDF_TARGET_ESP32
    ESP_LOGD(TAG, "MCLK is pinned to GPIO%d on I2S%d", gpio_num, id);
    return ESP_OK;
}

/*---------------------------------------------------------------------------
                            I2S bus Public APIs
 ----------------------------------------------------------------------------
    Scope: Public
 ----------------------------------------------------------------------------*/
esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *tx_handle, i2s_chan_handle_t *rx_handle)
{
#if CONFIG_I2S_ENABLE_DEBUG_LOG
    esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
    /* Parameter validity check */
    I2S_NULL_POINTER_CHECK(TAG, chan_cfg);
    I2S_NULL_POINTER_CHECK(TAG, tx_handle || rx_handle);
    ESP_RETURN_ON_FALSE((chan_cfg->id >= 0 && chan_cfg->id < I2S_LL_GET(INST_NUM)) || chan_cfg->id == I2S_NUM_AUTO, ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id");
    ESP_RETURN_ON_FALSE(chan_cfg->dma_desc_num >= 2, ESP_ERR_INVALID_ARG, TAG, "there should be at least 2 DMA buffers");
    ESP_RETURN_ON_FALSE(chan_cfg->intr_priority >= 0 && chan_cfg->intr_priority <= 7, ESP_ERR_INVALID_ARG, TAG, "intr_priority should be within 0~7");
#if !SOC_HAS(PAU)
    ESP_RETURN_ON_FALSE(!chan_cfg->allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
#endif

    esp_err_t ret = ESP_OK;
    i2s_controller_t *i2s_obj = NULL;
    int id = chan_cfg->id;
    bool channel_found = false;
    uint8_t chan_search_mask = 0;
    chan_search_mask |= tx_handle ? I2S_DIR_TX : 0;
    chan_search_mask |= rx_handle ? I2S_DIR_RX : 0;

    /* Channel will be registered to one i2s port automatically if id is I2S_NUM_AUTO
     * Otherwise, the channel will be registered to the specific port. */
    if (id == I2S_NUM_AUTO) {
        for (int i = 0; i < I2S_LL_GET(INST_NUM) && !channel_found; i++) {
            i2s_obj = i2s_acquire_controller_obj(i);
            if (!i2s_obj) {
                continue;
            }
            channel_found = i2s_take_available_channel(i2s_obj, chan_search_mask);
        }
        ESP_RETURN_ON_FALSE(i2s_obj, ESP_ERR_NOT_FOUND, TAG, "get i2s object failed");
    } else {
        i2s_obj = i2s_acquire_controller_obj(id);
        ESP_RETURN_ON_FALSE(i2s_obj, ESP_ERR_NOT_FOUND, TAG, "get i2s object failed");
        channel_found = i2s_take_available_channel(i2s_obj, chan_search_mask);
    }
    ESP_GOTO_ON_FALSE(channel_found, ESP_ERR_NOT_FOUND, err, TAG, "no available channel found");
    /* Register and specify the tx handle */
    if (tx_handle) {
        ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_TX, chan_cfg->dma_desc_num),
                          err, TAG, "register I2S tx channel failed");
        i2s_obj->tx_chan->role = chan_cfg->role;
        i2s_obj->tx_chan->is_port_auto = id == I2S_NUM_AUTO;
        i2s_obj->tx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
        i2s_obj->tx_chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb;
        i2s_obj->tx_chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb;
        i2s_obj->tx_chan->dma.desc_num = chan_cfg->dma_desc_num;
        i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num;
        i2s_obj->tx_chan->start = i2s_tx_channel_start;
        i2s_obj->tx_chan->stop = i2s_tx_channel_stop;
        *tx_handle = i2s_obj->tx_chan;
        ESP_LOGD(TAG, "tx channel is registered on I2S%d successfully", i2s_obj->id);
    }
    /* Register and specify the rx handle */
    if (rx_handle) {
        ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num),
                          err, TAG, "register I2S rx channel failed");
        i2s_obj->rx_chan->role = chan_cfg->role;
        i2s_obj->rx_chan->is_port_auto = id == I2S_NUM_AUTO;
        i2s_obj->rx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED;
        i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num;
        i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num;
        i2s_obj->rx_chan->start = i2s_rx_channel_start;
        i2s_obj->rx_chan->stop = i2s_rx_channel_stop;
        *rx_handle = i2s_obj->rx_chan;
        ESP_LOGD(TAG, "rx channel is registered on I2S%d successfully", i2s_obj->id);
    }

    if ((tx_handle != NULL) && (rx_handle != NULL)) {
        i2s_obj->full_duplex = true;
    }
#if I2S_USE_RETENTION_LINK
    if (chan_cfg->allow_pd) {
        s_i2s_create_retention_module(i2s_obj);
    }
#endif

    return ESP_OK;
    /* i2s_obj allocated but register channel failed */
err:
    /* if the controller object has no channel, find the corresponding global object and destroy it */
    if (i2s_obj != NULL && i2s_obj->rx_chan == NULL && i2s_obj->tx_chan == NULL) {
        for (int i = 0; i < I2S_LL_GET(INST_NUM); i++) {
            if (i2s_obj == g_i2s.controller[i]) {
                i2s_destroy_controller_obj(&g_i2s.controller[i]);
                break;
            }
        }
    }
    return ret;
}

esp_err_t i2s_del_channel(i2s_chan_handle_t handle)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    ESP_RETURN_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel can't be deleted unless it is disabled");
    i2s_controller_t *i2s_obj = handle->controller;
    int __attribute__((unused)) id = i2s_obj->id;
    i2s_dir_t __attribute__((unused)) dir = handle->dir;
    bool is_bound = true;

#if SOC_I2S_HW_VERSION_2
    I2S_CLOCK_SRC_ATOMIC() {
        if (dir == I2S_DIR_TX) {
            i2s_ll_tx_disable_clock(handle->controller->hal.dev);
        } else {
            i2s_ll_rx_disable_clock(handle->controller->hal.dev);
        }
    }
#endif
#if SOC_I2S_SUPPORTS_APLL
    if (handle->apll_en) {
        /* Must switch back to D2CLK on ESP32-S2,
         * because the clock of some registers are bound to APLL,
         * otherwise, once APLL is disabled, the registers can't be updated anymore */
        I2S_CLOCK_SRC_ATOMIC() {
            if (handle->dir == I2S_DIR_TX) {
                i2s_ll_tx_clk_set_src(handle->controller->hal.dev, I2S_CLK_SRC_DEFAULT);
            } else {
                i2s_ll_rx_clk_set_src(handle->controller->hal.dev, I2S_CLK_SRC_DEFAULT);
            }
        }
        periph_rtc_apll_release();
    }
#endif
#if CONFIG_PM_ENABLE
    if (handle->pm_lock) {
        esp_pm_lock_delete(handle->pm_lock);
    }
#endif
    if (handle->reserve_gpio_mask) {
        i2s_output_gpio_revoke(handle, handle->reserve_gpio_mask);
    }
    if (handle->mode_info) {
        free(handle->mode_info);
    }
    if (handle->dma.desc) {
        i2s_free_dma_desc(handle);
    }
    if (handle->msg_queue) {
        vQueueDeleteWithCaps(handle->msg_queue);
    }
    if (handle->mutex) {
        vSemaphoreDeleteWithCaps(handle->mutex);
    }
    if (handle->binary) {
        vSemaphoreDeleteWithCaps(handle->binary);
    }
#if SOC_I2S_HW_VERSION_1
    i2s_obj->chan_occupancy = 0;
#else
    i2s_obj->chan_occupancy &= ~(uint32_t)dir;
#endif
    if (handle->dma.dma_chan) {
#if SOC_GDMA_SUPPORTED
        gdma_disconnect(handle->dma.dma_chan);
        gdma_del_channel(handle->dma.dma_chan);
#else
        esp_intr_free(handle->dma.dma_chan);
#endif
    }
    if (handle == i2s_obj->tx_chan) {
        free(i2s_obj->tx_chan);
        i2s_obj->tx_chan = NULL;
        i2s_obj->full_duplex = false;
    } else if (handle == i2s_obj->rx_chan) {
        free(i2s_obj->rx_chan);
        i2s_obj->rx_chan = NULL;
        i2s_obj->full_duplex = false;
    } else {
        /* Indicate the delete channel is an unbound free channel */
        is_bound = false;
        free(handle);
    }

    /* If the delete channel was bound to a controller before,
       we need to destroy this controller object if there is no channel any more */
    if (is_bound) {
        if (!(i2s_obj->tx_chan) && !(i2s_obj->rx_chan)) {
            i2s_destroy_controller_obj(&g_i2s.controller[i2s_obj->id]);
        }
        ESP_LOGD(TAG, "%s channel on I2S%d deleted", dir == I2S_DIR_TX ? "tx" : "rx", id);
    }

    return ESP_OK;
}

esp_err_t i2s_channel_get_info(i2s_chan_handle_t handle, i2s_chan_info_t *chan_info)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    I2S_NULL_POINTER_CHECK(TAG, chan_info);

    /* Find whether the handle is a registered i2s handle or still available */
    for (int i = 0; i < I2S_LL_GET(INST_NUM); i++) {
        if (g_i2s.controller[i] != NULL) {
            if (g_i2s.controller[i]->tx_chan == handle ||
                    g_i2s.controller[i]->rx_chan == handle) {
                goto found;
            }
        }
    }
    return ESP_ERR_NOT_FOUND;
found:
    /* Assign the handle information */
    xSemaphoreTake(handle->mutex, portMAX_DELAY);
    chan_info->id = handle->controller->id;
    chan_info->dir = handle->dir;
    chan_info->role = handle->role;
    chan_info->mode = handle->mode;
    chan_info->total_dma_buf_size = handle->state >= I2S_CHAN_STATE_READY ? handle->dma.desc_num * handle->dma.buf_size : 0;
    if (handle->controller->full_duplex) {
        if (handle->dir == I2S_DIR_TX) {
            chan_info->pair_chan = handle->controller->rx_chan;
        } else {
            chan_info->pair_chan = handle->controller->tx_chan;
        }
    } else {
        chan_info->pair_chan = NULL;
    }
    xSemaphoreGive(handle->mutex);

    return ESP_OK;
}

esp_err_t i2s_channel_enable(i2s_chan_handle_t handle)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);

    esp_err_t ret = ESP_OK;

    xSemaphoreTake(handle->mutex, portMAX_DELAY);
    ESP_GOTO_ON_FALSE(handle->state == I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, err, TAG, "the channel has already enabled or not initialized");
#if CONFIG_PM_ENABLE
    esp_pm_lock_acquire(handle->pm_lock);
#endif
    handle->start(handle);
    handle->state = I2S_CHAN_STATE_RUNNING;
    if (handle->dir == I2S_DIR_RX) {
        /* RX queue is reset when the channel is enabled
           In case legacy data received during disable process */
        xQueueReset(handle->msg_queue);
    }
    xSemaphoreGive(handle->mutex);
    /* Give the binary semaphore to enable reading / writing task */
    xSemaphoreGive(handle->binary);

    ESP_LOGD(TAG, "i2s %s channel enabled", handle->dir == I2S_DIR_TX ? "tx" : "rx");
    return ret;

err:
    xSemaphoreGive(handle->mutex);
    return ret;
}

esp_err_t i2s_channel_disable(i2s_chan_handle_t handle)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    esp_err_t ret = ESP_OK;

    xSemaphoreTake(handle->mutex, portMAX_DELAY);
    ESP_GOTO_ON_FALSE(handle->state > I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, err, TAG, "the channel has not been enabled yet");
    /* Update the state to force quit the current reading/writing operation */
    handle->state = I2S_CHAN_STATE_READY;
    /* Waiting for reading/wrinting operation quit
     * It should be acquired before assigning the pointer to NULL,
     * otherwise may cause NULL pointer panic while reading/writing threads haven't release the lock */
    xSemaphoreTake(handle->binary, portMAX_DELAY);
    /* Reset the descriptor pointer */
    handle->dma.curr_ptr = NULL;
    handle->dma.rw_pos = 0;
    handle->stop(handle);
    if (handle->dir == I2S_DIR_TX) {
        /* TX queue is reset when the channel is disabled
           In case the queue is wrongly reset after preload the data */
        xQueueReset(handle->msg_queue);
    }
#if CONFIG_PM_ENABLE
    esp_pm_lock_release(handle->pm_lock);
#endif
    xSemaphoreGive(handle->mutex);
    ESP_LOGD(TAG, "i2s %s channel disabled", handle->dir == I2S_DIR_TX ? "tx" : "rx");
    return ret;

err:
    xSemaphoreGive(handle->mutex);
    return ret;
}

esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, size_t size, size_t *bytes_loaded)
{
    I2S_NULL_POINTER_CHECK(TAG, tx_handle);
    ESP_RETURN_ON_FALSE(tx_handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "this channel is not tx channel");
    ESP_RETURN_ON_FALSE(tx_handle->state == I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, TAG, "data can only be preloaded when the channel is READY");

    uint8_t *data_ptr = (uint8_t *)src;
    size_t remain_bytes = size;
    size_t total_loaded_bytes = 0;
    esp_err_t ret = ESP_OK;

    xSemaphoreTake(tx_handle->mutex, portMAX_DELAY);

    /* The pre-load data will be loaded from the first descriptor */
    if (tx_handle->dma.curr_ptr == NULL) {
        xQueueReset(tx_handle->msg_queue);
        /* Push the rest of descriptors to the queue */
        for (int i = 1; i < tx_handle->dma.desc_num; i++) {
            ESP_GOTO_ON_FALSE(xQueueSend(tx_handle->msg_queue, &(tx_handle->dma.desc[i]->buf), 0) == pdTRUE,
                              ESP_FAIL, err, TAG, "Failed to push the descriptor to the queue");
        }
        tx_handle->dma.curr_ptr = (void *)tx_handle->dma.desc[0]->buf;
        tx_handle->dma.rw_pos = 0;
    }

    /* Loop until no bytes in source buff remain or the descriptors are full */
    while (remain_bytes) {
        if (tx_handle->dma.rw_pos == tx_handle->dma.buf_size) {
            if (xQueueReceive(tx_handle->msg_queue, &(tx_handle->dma.curr_ptr), 0) == pdFALSE) {
                break;
            }
            tx_handle->dma.rw_pos = 0;
        }
        size_t bytes_can_load = remain_bytes > (tx_handle->dma.buf_size - tx_handle->dma.rw_pos) ?
                                (tx_handle->dma.buf_size - tx_handle->dma.rw_pos) : remain_bytes;
        /* When all the descriptors has loaded data, no more bytes can be loaded, break directly */
        if (bytes_can_load == 0) {
            break;
        }
        /* Load the data from the last loaded position */
        memcpy((uint8_t *)(tx_handle->dma.curr_ptr + tx_handle->dma.rw_pos), data_ptr, bytes_can_load);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
        esp_cache_msync(tx_handle->dma.curr_ptr, tx_handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
#endif
        data_ptr += bytes_can_load;             // Move forward the data pointer
        total_loaded_bytes += bytes_can_load;   // Add to the total loaded bytes
        remain_bytes -= bytes_can_load;         // Update the remaining bytes to be loaded
        tx_handle->dma.rw_pos += bytes_can_load;   // Move forward the dma buffer position
    }
    *bytes_loaded = total_loaded_bytes;

err:
    xSemaphoreGive(tx_handle->mutex);

    return ret;
}

esp_err_t i2s_channel_write(i2s_chan_handle_t handle, const void *src, size_t size, size_t *bytes_written, uint32_t timeout_ms)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "this channel is not tx channel");

    esp_err_t ret = ESP_OK;
    char *data_ptr;
    char *src_byte;
    size_t bytes_can_write;
    if (bytes_written) {
        *bytes_written = 0;
    }

    /* The binary semaphore can only be taken when the channel has been enabled and no other writing operation in progress */
    ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->binary, pdMS_TO_TICKS(timeout_ms)) == pdTRUE, ESP_ERR_INVALID_STATE, TAG, "The channel is not enabled");
    src_byte = (char *)src;
    while (size > 0 && handle->state == I2S_CHAN_STATE_RUNNING) {
        /* Acquire the new DMA buffer while:
         * 1. The current buffer is fully filled
         * 2. The current buffer is not set
         * 3. The queue is almost full, i.e., the curr_ptr is nearly to be invalid
         */
        if (handle->dma.rw_pos == handle->dma.buf_size || handle->dma.curr_ptr == NULL || uxQueueSpacesAvailable(handle->msg_queue) <= 1) {
            if (xQueueReceive(handle->msg_queue, &(handle->dma.curr_ptr), pdMS_TO_TICKS(timeout_ms)) == pdFALSE) {
                ret = ESP_ERR_TIMEOUT;
                break;
            }
            handle->dma.rw_pos = 0;
        }
        data_ptr = (char *)handle->dma.curr_ptr;
        data_ptr += handle->dma.rw_pos;
        bytes_can_write = handle->dma.buf_size - handle->dma.rw_pos;
        if (bytes_can_write > size) {
            bytes_can_write = size;
        }
        memcpy(data_ptr, src_byte, bytes_can_write);
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
        esp_cache_msync(handle->dma.curr_ptr, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
#endif
        size -= bytes_can_write;
        src_byte += bytes_can_write;
        handle->dma.rw_pos += bytes_can_write;
        if (bytes_written) {
            (*bytes_written) += bytes_can_write;
        }
    }
    xSemaphoreGive(handle->binary);

    return ret;
}

esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms)
{
    I2S_NULL_POINTER_CHECK(TAG, handle);
    ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_RX, ESP_ERR_INVALID_ARG, TAG, "this channel is not rx channel");

    esp_err_t ret = ESP_OK;
    uint8_t *data_ptr;
    uint8_t *dest_byte;
    int bytes_can_read;
    if (bytes_read) {
        *bytes_read = 0;
    }
    dest_byte = (uint8_t *)dest;
    /* The binary semaphore can only be taken when the channel has been enabled and no other reading operation in progress */
    ESP_RETURN_ON_FALSE(xSemaphoreTake(handle->binary, pdMS_TO_TICKS(timeout_ms)) == pdTRUE, ESP_ERR_INVALID_STATE, TAG, "The channel is not enabled");
    while (size > 0 && handle->state == I2S_CHAN_STATE_RUNNING) {
        /* Acquire the new DMA buffer while:
         * 1. The current buffer is fully filled
         * 2. The current buffer is not set
         * 3. The queue is almost full, i.e., the curr_ptr is nearly to be invalid
         */
        if (handle->dma.rw_pos == handle->dma.buf_size || handle->dma.curr_ptr == NULL || uxQueueSpacesAvailable(handle->msg_queue) <= 1) {
            if (xQueueReceive(handle->msg_queue, &(handle->dma.curr_ptr), pdMS_TO_TICKS(timeout_ms)) == pdFALSE) {
                ret = ESP_ERR_TIMEOUT;
                break;
            }
            handle->dma.rw_pos = 0;
        }
        data_ptr = (uint8_t *)handle->dma.curr_ptr;
        data_ptr += handle->dma.rw_pos;
        bytes_can_read = handle->dma.buf_size - handle->dma.rw_pos;
        if (bytes_can_read > (int)size) {
            bytes_can_read = size;
        }
        memcpy(dest_byte, data_ptr, bytes_can_read);
        size -= bytes_can_read;
        dest_byte += bytes_can_read;
        handle->dma.rw_pos += bytes_can_read;
        if (bytes_read) {
            (*bytes_read) += bytes_can_read;
        }
    }
    xSemaphoreGive(handle->binary);

    return ret;
}

esp_err_t i2s_channel_tune_rate(i2s_chan_handle_t handle, const i2s_tuning_config_t *tune_cfg, i2s_tuning_info_t *tune_info)
{
    /** We tune the sample rate via the MCLK clock.
     *  Because the sample rate is decided by MCLK eventually,
     *  and MCLK has a higher resolution which can be tuned more precisely.
     */

    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "NULL pointer");
    ESP_RETURN_ON_FALSE(!handle->is_external, ESP_ERR_NOT_SUPPORTED, TAG, "Not support to tune rate for external mclk");

    /* If no tuning configuration given, just return the current information */
    if (tune_cfg == NULL) {
        xSemaphoreTake(handle->mutex, portMAX_DELAY);
        goto result;
    }
    ESP_RETURN_ON_FALSE(tune_cfg->max_delta_mclk >= tune_cfg->min_delta_mclk, ESP_ERR_INVALID_ARG, TAG, "invalid range");

    uint32_t new_mclk = 0;

    /* Get the new MCLK according to the tuning operation */
    switch (tune_cfg->tune_mode) {
    case I2S_TUNING_MODE_ADDSUB:
        new_mclk = handle->curr_mclk_hz + tune_cfg->tune_mclk_val;
        break;
    case I2S_TUNING_MODE_SET:
        new_mclk = tune_cfg->tune_mclk_val;
        break;
    case I2S_TUNING_MODE_RESET:
        new_mclk = handle->origin_mclk_hz;
        break;
    default:
        return ESP_ERR_INVALID_ARG;
    }
    /* Check if the tuned mclk is within the supposed range */
    if ((int32_t)new_mclk - (int32_t)handle->origin_mclk_hz > tune_cfg->max_delta_mclk) {
        new_mclk = handle->origin_mclk_hz + tune_cfg->max_delta_mclk;
    } else if ((int32_t)new_mclk - (int32_t)handle->origin_mclk_hz < tune_cfg->min_delta_mclk) {
        new_mclk = handle->origin_mclk_hz + tune_cfg->min_delta_mclk;
    }
    xSemaphoreTake(handle->mutex, portMAX_DELAY);
#if SOC_CLK_APLL_SUPPORTED
    if (handle->clk_src == I2S_CLK_SRC_APLL) {
        periph_rtc_apll_release();
        handle->sclk_hz = i2s_set_get_apll_freq(new_mclk);
        periph_rtc_apll_acquire();
    }
#endif
    /* Calculate the new divider */
    hal_utils_clk_div_t mclk_div = {};
    i2s_hal_calc_mclk_precise_division(handle->sclk_hz, new_mclk, &mclk_div);
    /* mclk_div = sclk / mclk >= 2 */
    if (mclk_div.integer < 2) {
        return ESP_ERR_INVALID_ARG;
    }
    /* Set the new divider for MCLK */
    I2S_CLOCK_SRC_ATOMIC() {
        if (handle->dir == I2S_DIR_TX) {
            i2s_ll_tx_set_mclk(handle->controller->hal.dev, &mclk_div);
#if SOC_I2S_HW_VERSION_2
            i2s_ll_tx_update(handle->controller->hal.dev);
#endif
        } else {
            i2s_ll_rx_set_mclk(handle->controller->hal.dev, &mclk_div);
#if SOC_I2S_HW_VERSION_2
            i2s_ll_rx_update(handle->controller->hal.dev);
#endif
        }
    }
    /* Save the current mclk frequency */
    handle->curr_mclk_hz = (uint32_t)(((uint64_t)handle->sclk_hz * mclk_div.denominator) /
                                      (mclk_div.integer * mclk_div.denominator + mclk_div.numerator));
result:
    /* Assign the information if needed */
    if (tune_info) {
        tune_info->curr_mclk_hz = handle->curr_mclk_hz;
        tune_info->delta_mclk_hz = (int32_t)handle->curr_mclk_hz - (int32_t)handle->origin_mclk_hz;
        uint32_t tot_size = handle->dma.buf_size * handle->dma.desc_num;
        uint32_t used_size = 0;
        if (handle->dir == I2S_DIR_TX) {
            used_size = uxQueueSpacesAvailable(handle->msg_queue) * handle->dma.buf_size + handle->dma.rw_pos;
        } else {
            used_size = uxQueueMessagesWaiting(handle->msg_queue) * handle->dma.buf_size + handle->dma.buf_size - handle->dma.rw_pos;
        }
        tune_info->water_mark = used_size * 100 / tot_size;
    }
    xSemaphoreGive(handle->mutex);
    return ESP_OK;
}

#if SOC_I2S_SUPPORTS_TX_SYNC_CNT
uint32_t i2s_sync_get_bclk_count(i2s_chan_handle_t tx_handle)
{
    return i2s_ll_tx_get_bclk_sync_count(tx_handle->controller->hal.dev);
}

uint32_t i2s_sync_get_fifo_count(i2s_chan_handle_t tx_handle)
{
    return i2s_ll_tx_get_fifo_sync_count(tx_handle->controller->hal.dev);
}

void i2s_sync_reset_bclk_count(i2s_chan_handle_t tx_handle)
{
    i2s_ll_tx_reset_bclk_sync_counter(tx_handle->controller->hal.dev);
}

void i2s_sync_reset_fifo_count(i2s_chan_handle_t tx_handle)
{
    i2s_ll_tx_reset_fifo_sync_counter(tx_handle->controller->hal.dev);
}
#endif  // SOC_I2S_SUPPORTS_TX_SYNC_CNT

#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
uint32_t i2s_sync_get_fifo_sync_diff_count(i2s_chan_handle_t tx_handle)
{
    return i2s_ll_tx_get_fifo_sync_diff_count(tx_handle->controller->hal.dev);
}

void i2s_sync_reset_fifo_sync_diff_count(i2s_chan_handle_t tx_handle)
{
    i2s_ll_tx_reset_fifo_sync_diff_counter(tx_handle->controller->hal.dev);
}

esp_err_t i2s_sync_enable_hw_fifo_sync(i2s_chan_handle_t tx_handle, bool enable)
{
    if (tx_handle->dir == I2S_DIR_RX) {
        return ESP_ERR_NOT_SUPPORTED;
    }
    i2s_ll_tx_enable_hw_fifo_sync(tx_handle->controller->hal.dev, enable);
    return ESP_OK;
}

esp_err_t i2s_sync_config_hw_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_sync_fifo_sync_config_t *config)
{
    if (!(tx_handle && config)) {
        return ESP_ERR_INVALID_ARG;
    }
    if (tx_handle->dir == I2S_DIR_RX) {
        return ESP_ERR_NOT_SUPPORTED;
    }
    if (config->sw_high_thresh < config->hw_low_thresh) {
        return ESP_ERR_INVALID_ARG;
    }

    i2s_ll_tx_set_etm_sync_ideal_cnt(tx_handle->controller->hal.dev, config->ideal_cnt);
    i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(tx_handle->controller->hal.dev, config->sw_high_thresh);
    i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(tx_handle->controller->hal.dev, config->hw_low_thresh);
    i2s_ll_tx_set_hw_fifo_sync_suppl_mode(tx_handle->controller->hal.dev, (uint32_t)config->suppl_mode);
    if (config->suppl_mode == I2S_SYNC_SUPPL_MODE_STATIC_DATA) {
        i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(tx_handle->controller->hal.dev, config->suppl_data);
    }
    return ESP_OK;
}
#endif
